package com.example.demo.aop;

import com.example.demo.myannotation.CustomCacheEvict;
import com.example.demo.myannotation.CustomCachePut;
import com.example.demo.myannotation.CustomCacheable;
import com.example.demo.pojo.Person;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.ehcache.Cache;
import org.ehcache.CacheManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

@Aspect
@Component
public class CustomAspect {

    private final Logger logger= LoggerFactory.getLogger(this.getClass().getName());

    @Resource(name = "ehcacheCacheManager")
    private CacheManager cacheManager;

    @Resource(name = "redisCacheManager")
    private RedisCacheManager redisCacheManager;

    @Pointcut("@annotation(com.example.demo.myannotation.CustomCacheable)")
    public void fun(){

    }

    @Pointcut("@annotation(com.example.demo.myannotation.CustomCachePut)" +
            "||@annotation(com.example.demo.myannotation.CustomCacheEvict)")
    public void fun2(){

    }

    @Around("fun()")
    public Person CustomCacheable(ProceedingJoinPoint joinPoint){
        Person result=null;

        Method method=((MethodSignature)joinPoint.getSignature()).getMethod();
        CustomCacheable cacheable=method.getDeclaredAnnotation(CustomCacheable.class);
        String value=cacheable.value();
        String key=getKeyValue(joinPoint,method,cacheable.key());

        Cache<String, Person> cache=cacheManager.getCache(value,String.class,Person.class);

        result=cache.get(key);
        if (result!=null){
            return result;
        }else {
            org.springframework.cache.Cache redisCache=redisCacheManager.getCache(value);

            assert redisCache != null;
            result=redisCache.get(key,Person.class);
            if (result!=null){
                return result;
            }else {
                try {
                    result=(Person) joinPoint.proceed();

                    cache.put(key,result);
                    redisCache.put(key,result);
                }catch (Throwable e){
                    e.printStackTrace();
                }
            }
        }

        return result;
    }

    @Around("fun2()")
    public Person CustomCachePutOrEvict(ProceedingJoinPoint joinPoint){
        Person result=null;

        Method method=((MethodSignature)joinPoint.getSignature()).getMethod();

        String keyValue;
        if (method.isAnnotationPresent(CustomCachePut.class)){
            CustomCachePut cachePut=method.getDeclaredAnnotation(CustomCachePut.class);
            String value=cachePut.value();
            String key=cachePut.key();

            Cache<String, Person> cache=cacheManager.getCache(value,String.class,Person.class);
            org.springframework.cache.Cache redisCache=redisCacheManager.getCache(value);

            try {
                result=(Person) joinPoint.proceed();

                keyValue=getKeyValue2(joinPoint,method,key,result);
                cache.put(keyValue,result);

                assert redisCache != null;
                redisCache.put(keyValue,result);
            }catch (Throwable e){
                e.printStackTrace();
            }
        }else {
            CustomCacheEvict cacheEvict=method.getDeclaredAnnotation(CustomCacheEvict.class);
            String value=cacheEvict.value();
            String key=cacheEvict.key();

            Cache<String,Person> cache=cacheManager.getCache(value,String.class,Person.class);
            org.springframework.cache.Cache redisCache=redisCacheManager.getCache(value);

            try {
                result=(Person) joinPoint.proceed();

                keyValue=getKeyValue2(joinPoint,method,key,result);
                cache.remove(keyValue);

                assert redisCache != null;
                redisCache.evictIfPresent(keyValue);
            }catch (Throwable e){
                e.printStackTrace();
            }
        }

        return result;
    }

    private String getKeyValue(ProceedingJoinPoint joinPoint,Method method,String key) {
        if (key==null||!key.startsWith("#")){
            return null;
        }

        String result=null;
        if (!key.contains(".")){
            String name=key.substring(1);

            Object[] args=joinPoint.getArgs();
            Parameter[] parameters=method.getParameters();

            for (int i=0;i<parameters.length;i++){
                if (parameters[i].getName().equals(name)){
                    result=args[i].toString();
                    break;
                }
            }
        }else {
            String[] s=key.substring(1).split("\\.");
            Object[] args = joinPoint.getArgs();
            Parameter[] parameters=method.getParameters();

            for (int i=0;i<parameters.length;i++){
                if (parameters[i].getName().equals(s[0])){
                    try {
                        Field field=parameters[i].getType().getDeclaredField(s[1]);
                        field.setAccessible(true);

                        result= field.get(args[i]).toString();
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }
        }

        return result;
    }

    private String getKeyValue2(ProceedingJoinPoint joinPoint,Method method,String key,Person returnValue) {
        if (key==null||!key.startsWith("#")){
            return null;
        }

        String result=null;
        if (!key.contains(".")){
            String name=key.substring(1);

            if (!name.equals("result")){
                Object[] args=joinPoint.getArgs();
                Parameter[] parameters=method.getParameters();

                for (int i=0;i<parameters.length;i++){
                    if (parameters[i].getName().equals(name)){
                        result=args[i].toString();
                        break;
                    }
                }
            }else {
                Class<?> returnType=method.getReturnType();
                try {
                    Field field=returnType.getDeclaredField(name);
                    field.setAccessible(true);

                    result = field.get(returnValue).toString();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }else {
            String[] s=key.substring(1).split("\\.");

            if (!s[0].equals("result")){
                Object[] args = joinPoint.getArgs();
                Parameter[] parameters=method.getParameters();

                for (int i=0;i<parameters.length;i++){
                    if (parameters[i].getName().equals(s[0])){
                        try {
                            Field field=parameters[i].getType().getDeclaredField(s[1]);
                            field.setAccessible(true);

                            result= field.get(args[i]).toString();
                        }catch (Exception e){
                            e.printStackTrace();
                        }
                    }
                }
            }else {
                Class<?> returnType=method.getReturnType();
                try {
                    Field field=returnType.getDeclaredField(s[1]);
                    field.setAccessible(true);

                    result = field.get(returnValue).toString();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }

        return result;
    }
}
